package com.naiterui.ehp.bs.recommend.service.impl;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.common.repository.page.PageParamVO;
import com.naiterui.common.repository.page.PageVO;
import com.naiterui.ehp.bp.bo.doctor.RecomDoctorBO;
import com.naiterui.ehp.bp.bo.im.RequireDrugBO.RequireDrugItemBO;
import com.naiterui.ehp.bp.bo.patient.RequireReplyTemplateMsgBO;
import com.naiterui.ehp.bp.constants.AccountStatusEnum;
import com.naiterui.ehp.bp.constants.DoctorPatientRelation;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.biz.AppJumpProtocol;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.notice.AppNoticeUtil;
import com.naiterui.ehp.bp.utils.notice.AppPushUtil;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.recommend.bean.*;
import com.naiterui.ehp.bs.recommend.exception.ExceptionCodes;
import com.naiterui.ehp.bs.recommend.remote.IB2cFeignClient;
import com.naiterui.ehp.bs.recommend.remote.IDoctorFeignClient;
import com.naiterui.ehp.bs.recommend.remote.IPatientFeignClient;
import com.naiterui.ehp.bs.recommend.remote.IRecommendRemoter;
import com.naiterui.ehp.bs.recommend.repository.mysql.*;
import com.naiterui.ehp.bs.recommend.service.IMedicationRequireService;
import com.naiterui.ehp.bs.recommend.vo.MedicationRequireDetailVO;
import com.naiterui.ehp.bs.recommend.vo.require.DrRequireVO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class MedicationRequireServiceImpl implements IMedicationRequireService {
    private static Logger LOGGER = LoggerFactory.getLogger(MedicationRequireServiceImpl.class);

    @Autowired
    private IMedicationRequireRepository medicationRequireDao;
    @Autowired
    private IMedicationRecomRepository medicationRecomDao;
    @Autowired
    private IRecommendRemoter remoter;
    @Autowired
    private IMedicationRequireDetailRepository medicationRequireDetailDao;
    @Autowired
    private IMedicationRecomDetailRepository medicationRecomDetailDao;

    @Autowired
    private IMedicationRecomInfoRepository medicationRecomInfoRepository;
    @Autowired
    private IMedicationRecomDiagnosisRepository medicationRecomDiagnosisRepository;

    @Autowired
    private IPatientFeignClient patientFeignClient;

    @Autowired
    private IB2cFeignClient b2cFeignClient;

    @Autowired
    private IDoctorFeignClient doctorFeignClient;

    @Override
    public PageVO<DrRequireVO> getDrRequireList(Long doctorId, Integer status, PageParamVO pageParamVO) throws BusinessException {
        Pageable pageable = pageParamVO.getPageable();
        Page<MedicationRequire> page;
        if (status == DrRequireVO.REQUIRE_HANDLE_STATUS_DEFAULT) {
            page = medicationRequireDao.findByDoctorIdAndApplyStatusOrderByApplyStatusAscCreatedAtDesc(
                    doctorId, MedicationRequire.APPLY_STATUS_DEFAULT, pageable);
        } else if (status == DrRequireVO.REQUIRE_HANDLE_STATUS_ALREADY) {
            page = medicationRequireDao.findByDoctorIdAndApplyStatusGreaterThanOrderByApplyStatusAscCreatedAtDesc(
                    doctorId, MedicationRequire.APPLY_STATUS_DEFAULT, pageable);
        } else {
            page = medicationRequireDao.findByDoctorIdOrderByApplyStatusAscCreatedAtDesc(doctorId, pageable);
        }

        //关联推荐集合
        List<Long> recommendIds = new ArrayList<>();
        List<Long> requireIds = new ArrayList<>();
        List<DrRequireVO> result = page.getContent().stream().map(s -> {
            DrRequireVO vo = new DrRequireVO();
            vo.setRequireId(s.getId());
            vo.setRecommendId(s.getFormerRecommendId());
            vo.setRequiredTime(s.getCreatedAt().getTime());
            vo.setHandleStatus(s.getApplyStatus() == 0 ? DrRequireVO.REQUIRE_HANDLE_STATUS_DEFAULT : DrRequireVO.REQUIRE_HANDLE_STATUS_ALREADY);
            vo.setApplyStatus(s.getApplyStatus());
            vo.setRemark(s.getRemark());

            recommendIds.add(s.getFormerRecommendId());
            requireIds.add(s.getId());
            return vo;
        }).collect(Collectors.toList());

        //求药关联推荐信息
        List<MedicationRecom> recomList = medicationRecomDao.findByIdIn(recommendIds);
        Map<Long, MedicationRecom> recomMap = recomList.stream()
                                                       .collect(Collectors.toMap(MedicationRecom::getId, s -> s));

        //推荐关联患者信息
        List<DrMedicationRecomInfo> recomInfoList = medicationRecomInfoRepository.findByRecommendIdIn(recommendIds);
        Map<Long, DrMedicationRecomInfo> recomInfoMap = recomInfoList.stream()
                                                                     .collect(Collectors.toMap(DrMedicationRecomInfo::getRecommendId, s -> s));

        //拼装处方关联诊断信息
        List<DrMedicationRecomDiagnosis> diagnosisList = medicationRecomDiagnosisRepository.findByRecommendIdIn(recommendIds);
        Map<Long, List<String>> diagnosisMap = diagnosisList.stream().collect(
                Collectors.groupingBy(DrMedicationRecomDiagnosis::getRecommendId, Collectors.mapping(s -> s.getName(), Collectors.toList())));

        //求药申请的第一个药品信息
        List<MedicationRequireDetail> requireDetailList = medicationRequireDetailDao.findByRequireIdIn(requireIds);
        Map<Long, MedicationRequireDetailVO> requireDetailMap = new HashMap<>();
        for (MedicationRequireDetail detail : requireDetailList) {
            MedicationRequireDetailVO detailVO = new MedicationRequireDetailVO();
            BeanUtils.copyProperties(detail, detailVO);
            detailVO.setQuantityUnit(StringUtils.isBlank(detail.getQuantityUnit()) ? "" : detail.getQuantityUnit());
            requireDetailMap.putIfAbsent(detail.getRequireId(), detailVO);
        }

        //求药关联的新处方
        List<MedicationRecom> newRecomList = medicationRecomDao.findByRequireIdInAndForceRecom(requireIds, MedicationRecom.FORCE_RECOM_STATUS_SEND);
        Map<Long, Long> newRecomMap = newRecomList.stream().collect(Collectors.toMap(MedicationRecom::getRequireId, MedicationRecom::getId, (o, n) -> n));
        result.forEach(s -> {
            //处方
            MedicationRecom recom = recomMap.get(s.getRecommendId());
            if (null != recom) {
                s.setRecomTime(recom.getCreatedAt().getTime());
                s.setSerialNumber(recom.getSerialNumber());
            }

            //患者
            DrMedicationRecomInfo recomInfo = recomInfoMap.get(s.getRecommendId());
            if (null != recomInfo) {
                s.setPatientName(recomInfo.getPatientName());
                s.setPatientAge(recomInfo.getPatientAge());
                s.setPatientAgeUnit(recomInfo.getPatientAgeUnit());
                s.setPatientGender(recomInfo.getPatientGender());
                s.setPatientId(recomInfo.getPatientId());
                s.setPatientAgeStr(recomInfo.getPatientAgeStr());
            }

            //诊断
            s.setDiagnosis(diagnosisMap.get(s.getRecommendId()));

            //药品
            s.setDetailVO(requireDetailMap.get(s.getRequireId()));

            //关联新处方
            s.setNewRecomId(newRecomMap.get(s.getRequireId()));

        });
        return PageVO.newBuilder()
                     .result(result)
                     .pageSize(pageParamVO.getNum())
                     .pageNo(pageParamVO.getPage())
                     .totalCount((int) page.getTotalElements())
                     .build();
    }

    @Override
    public void requireSuspend(Long requireId, String remark) throws BusinessException {
        MedicationRequire require = handleRequire(requireId, MedicationRequire.APPLY_STATUS_SUSPEND, remark);

        DrMedicationRecomInfo recomInfo = medicationRecomInfoRepository.findFirstByRecommendId(require.getFormerRecommendId());

        //向患者发送暂停用药通知
        patientFeignClient.sendNotice(RequireReplyTemplateMsgBO.builder()
                                                               .doctorName(recomInfo.getDoctorName())
                                                               .doctorId(recomInfo.getDoctorId())
                                                               .patientId(recomInfo.getPatientId())
                                                               .recomId(recomInfo.getRecommendId())
                                                               .processRemark(remark)
                                                               .processTime(DateUtil.formatDate(require.getChangedAt(), DateUtil.DATE_TIME_FORMAT))
                                                               .processStaus(MedicationRequire.APPLY_STATUS_SUSPEND_NAME)
                                                               .build());
    }

    @Override
    public MedicationRequire handleRequire(Long requireId, Integer status, String remark) throws BusinessException {
        MedicationRequire require = get(requireId);
        if (require.getApplyStatus() != MedicationRequire.APPLY_STATUS_DEFAULT) {
            throw new BusinessException(ExceptionCodes.REQUIRE_ALREADY_HANDLE);
        }
        require.setApplyStatus(status);
        require.setRemark(remark);
        require.setChangedAt(new Date());
        medicationRequireDao.update(require);
        return require;
    }

    /**
     * 药方失效后患者再次求药
     *
     * @param patientId
     * @param recommendId 上次推荐的推荐ID
     * @return
     * @throws BusinessException
     * @Author Ldl
     * @Date 2018年1月9日
     * Long, Long)
     * @since 1.0.0
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addAgainRequire(Long patientId, Long recommendId) throws BusinessException {
        // 根据推荐id查询原来的求药信息
        MedicationRecom recom = medicationRecomDao.get(recommendId);
        Long doctorId = recom.getDoctorId();
        //患者再次购药先判断医生状态，停用直接返回异常信息
        RecomDoctorBO recomDoctorInfo = doctorFeignClient.getRecomDoctorInfo(doctorId);
        LOGGER.info("患者再次购药 医生信息：recomDoctorInfo={}",recomDoctorInfo);
        if (recomDoctorInfo == null) {
            throw new BusinessException(ExceptionCodes.DOCTORID_NOT_EXITS);
        } else if (AccountStatusEnum.FORBIDDEN.getStatus().equals(recomDoctorInfo.getAccountStatus())) {
            throw new BusinessException(ExceptionCodes.DOCTOR_ACCOUNT_DISABLE);
        }
        // 这里要先校验医患关系
        String doctorPatientRelationRedisKey = DoctorPatientRelation.getRedisKey(doctorId, patientId);
        Object doctorPatientRelation = RedisUtil.valueOps().getObject(doctorPatientRelationRedisKey);
        if (doctorPatientRelation != null && doctorPatientRelation.equals(DoctorPatientRelation.RELATION_FLAG_DELETE)) {
            throw new BusinessException(ExceptionCodes.DOCTOR_PATIENT_RELATION_DELETE);
        }
        Date lastRequireDate = recom.getLastRequireAt();
        // 已经申请过一次，则不能再次申请
        if (lastRequireDate != null) {
            throw new BusinessException(ExceptionCodes.REQUIRE_NOT_REPEAT);
        }
        recom.setLastRequireAt(new Date());
        medicationRecomDao.update(recom);

        // 保存新的购药消息
        long extId = System.currentTimeMillis();

        // 1.校验求药单外部ext_id是否重复
        long count = medicationRequireDao.countByDoctorIdAndPatientIdAndExtId(doctorId, patientId, extId + "");
        if (count > 0) {
            LOGGER.warn("[require]再次求药单外部求药号已存在,extId:{},doctorId:{},patientId:{}", extId, doctorId, patientId);
            throw new BusinessException(ExceptionCodes.DOCTOR_REQUIRE_CREATE_FAIL);
        }

        // 2.创建求药单
        long expiration = ConfigUtil.getInt("medication_require_expiration_min") * 60 * 1000;
        long spaceTime = ConfigUtil.getInt("medication_submit_require_space_min") * 60 * 1000;

        MedicationRequire againRequire = new MedicationRequire(doctorId, patientId, extId + "", expiration, spaceTime);
        againRequire.setFormerRecommendId(recommendId);
        againRequire.setApplyStatus(MedicationRequire.APPLY_STATUS_DEFAULT);
        medicationRequireDao.save(againRequire);

        // 3.创建求药单商品详情
        List<MedicationRecomDetail> lastRecomItems = medicationRecomDetailDao.findByRecommendId(recommendId);
        List<Long> pidList = new ArrayList<>();
        List<RequireDrugItemBO> items = new ArrayList<>();
        for (MedicationRecomDetail item : lastRecomItems) {
            MedicationRequireDetail detail = new MedicationRequireDetail(againRequire.getId(), item.getPid(),
                                                                         item.getSkuId(), item.getName(), item.getCommonName(), item.getQuantity(),
                                                                         extId + "");
            detail.setQuantityUnit(item.getQuantityUnit());

            RequireDrugItemBO requireItem = new RequireDrugItemBO(null, null, detail.getPid(),
                                                                  detail.getSkuId(), detail.getName(), detail.getQuantity(),
                                                                  detail.getCommonName());
            items.add(requireItem);
            pidList.add(item.getPid());
            medicationRequireDetailDao.save(detail);
        }

        //已申请处方从购物车删除
        b2cFeignClient.cleanCart(recommendId);

        ThreadPoolUtil.execute(() -> {
            try {
                //通知医生刷新首页接口
                AppNoticeUtil.NewRequire noticeInfo = new AppNoticeUtil.NewRequire(ConfigUtil.getString("doctor.require.message"));
                // amp服务地址
                String ampHost = ConfigUtil.getString("amp.host.url");
                AppNoticeUtil.sendSingleNotice(ampHost, doctorId, AppNoticeUtil.NoticeType.DR_NEW_REQUIRE, noticeInfo);
                // 给医生推送push消息
                AppPushUtil.sendDrPush(ConfigUtil.getString("push.message.server"), doctorId, "您收到一个新的续方申请，请及时处理", AppJumpProtocol.DR_INQUIRE_MEDICAL, "");
            } catch (Exception e){
                LOGGER.error(" 患者再次求药医生消息异常 recommendId={}", recommendId, e);
            }
        });
    }

    private MedicationRequire get(Long requireId) throws BusinessException {
        MedicationRequire require = medicationRequireDao.get(requireId);
        if (require == null) {
            throw new BusinessException(ExceptionCodes.REQUIRE_RECORD_NOT_EXIST);
        }
        return require;
    }
}
